package com.spzx.order.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.cart.api.RemoteCartService;
import com.spzx.cart.api.domain.CartInfo;
import com.spzx.common.core.constant.SecurityConstants;
import com.spzx.common.core.context.SecurityContextHolder;
import com.spzx.common.rabbit.constant.MqConst;
import com.spzx.common.rabbit.service.RabbitService;
import com.spzx.common.security.utils.SecurityUtils;
import com.spzx.order.api.domain.OrderInfo;
import com.spzx.order.api.domain.OrderItem;
import com.spzx.order.domain.*;
import com.spzx.order.mapper.OrderInfoMapper;
import com.spzx.order.mapper.OrderItemMapper;
import com.spzx.order.mapper.OrderLogMapper;
import com.spzx.order.service.IOrderInfoService;


import com.spzx.product.api.SpzxService;
import com.spzx.product.api.domain.ProductSku;
import com.spzx.product.api.domain.SkuLockVo;
import com.spzx.product.api.domain.SkuPrice;
import com.spzx.user.api.domain.UserAddress;
import com.spzx.user.api.model.RemoteUserAddressService;

import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements IOrderInfoService {
    @Autowired
    private OrderInfoMapper orderInfoMapper;

    @Autowired
    private OrderItemMapper orderItemMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RemoteCartService remoteCartService;

    @Autowired
    private RemoteUserAddressService remoteUserAddressService;

    @Autowired
    private OrderLogMapper orderLogMapper;

    @Autowired
    private SpzxService spzxService;

    @Autowired
    private RabbitService rabbitService;



    /**
     * 查询订单列表
     *
     * @param orderInfo 订单
     * @return 订单
     */
    @Override
    public List<OrderInfo> selectOrderInfoList(OrderInfo orderInfo) {
        return orderInfoMapper.selectOrderInfoList(orderInfo);
    }

    /**
     * 查询订单
     *
     * @param id 订单主键
     * @return 订单
     */
    @Override
    public OrderInfo selectOrderInfoById(Long id) {
        OrderInfo orderInfo = orderInfoMapper.selectById(id);
        List<OrderItem> orderItemList = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>().eq(OrderItem::getOrderId, id));
        orderInfo.setOrderItemList(orderItemList);
        return orderInfo;
    }

    @Override
    public TradeVo getTradeVo() {
        Long userId = SecurityUtils.getUserId();
        String cartKey = getCartKey(userId);
        BoundHashOperations<String, String, CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
        TradeVo tradeVo = new TradeVo();

//        // 怎么样得到 TradeVo
//        // "totalAmount" OrderInfo表里有
//        OrderInfo orderInfo = orderInfoMapper.selectById(userId);
//        tradeVo.setTotalAmount(orderInfo.getTotalAmount());
//
//        // "orderItemList" OrderInfo表里有
//        tradeVo.setOrderItemList(orderInfo.getOrderItemList());
//
//        //  "tradeNo" 交易号自己随机生成
//        String tradeNo = this.generateTradeNo(userId);
//        tradeVo.setTradeNo(tradeNo);
        List<CartInfo> cartInfoList = remoteCartService.getCartCheckedList(userId, SecurityConstants.INNER).getData();
        Assert.notEmpty(cartInfoList, "购物车不能为空");

        List<OrderItem> orderItemList = cartInfoList.stream().map(cartInfo -> {
            OrderItem orderItem = new OrderItem();
            BeanUtils.copyProperties(cartInfo, orderItem);
            return orderItem;
        }).collect(Collectors.toList());

        // 总金额
        BigDecimal sum = new BigDecimal("0");
        for (OrderItem orderItem : orderItemList) {
            sum = sum.add(orderItem.getSkuPrice().multiply(new BigDecimal(orderItem.getSkuNum())));
        }

        //  "tradeNo" 交易号自己随机生成
        String tradeNo = this.generateTradeNo(userId);
        tradeVo.setTradeNo(tradeNo);

        tradeVo.setTradeNo(tradeNo);
        tradeVo.setTotalAmount(sum);
        tradeVo.setOrderItemList(orderItemList);

        return tradeVo;
    }

    @Override
    public Long submitOrder(OrderForm orderForm) {
        Long userId = SecurityUtils.getUserId();
        Long userAddressId = orderForm.getUserAddressId();
        String tradeNo = orderForm.getTradeNo();

        // 校验
        Assert.hasText(tradeNo,"订单已经提交或者发生异常");
        String userTradeKey = "user:tradeNo:"+userId;
        // 比较交易码(流水号)是否一致，然后删除（比较和删除最好一起执行，避免无聊的人打开多个相同的页面一起提交订单）
        String delLua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(delLua);
        redisScript.setResultType(Long.class);
        Long execute = (Long) redisTemplate.execute(redisScript, Arrays.asList(userTradeKey), tradeNo);
        Assert.isTrue(execute!=0,"订单已提交或发生异常");

        // 补充功能，提交订单后，商家改变价格
        List<Long> skuIds = orderForm.getOrderItemList().stream().map(OrderItem::getSkuId).collect(Collectors.toList());
        List<SkuPrice> skuPrices = spzxService.getSkuPriceList(skuIds, SecurityConstants.INNER).getData();
        Map<Long,BigDecimal> priceMap =  skuPrices.stream().collect(Collectors.toMap(SkuPrice::getSkuId,SkuPrice::getSalePrice));
        for (OrderItem orderItem : orderForm.getOrderItemList()) {
            BigDecimal skuPrice = orderItem.getSkuPrice();
            Long skuId = orderItem.getSkuId();
            BigDecimal currentSalePrice = priceMap.get(skuId);// 实时价格
            int i = skuPrice.compareTo(currentSalePrice);
            if(i!=0){
                // 更新购物车价格信息
                remoteCartService.updateCartPrice(userId, SecurityConstants.INNER);
            }
            Assert.isTrue(i==0,"价格发生变化");
        }


        // 第一 获得用户收货地址 调用 user 服务
        UserAddress userAddress = remoteUserAddressService.getUserAddress(userAddressId, SecurityConstants.INNER).getData();
        Assert.notNull(userAddress,"用户地址获取失败");

        // 下单之前 锁单 （检查和锁定库存） skuId skuNum
        // 是否需要防止订单重复提交 其实可以不用，但为了确保更可靠，还是加上了分布式锁 ，还是可能跑到不同的服务器上 （前面有交易码确保了）
        List<SkuLockVo> skuLockVoList = new ArrayList<>();
        for (OrderItem orderItem:orderForm.getOrderItemList()) {
            Long skuId = orderItem.getSkuId();
            Integer skuNum = orderItem.getSkuNum();
            SkuLockVo skuLockVo = new SkuLockVo();
            skuLockVo.setSkuId(skuId);
            skuLockVo.setSkuNum(skuNum);
            skuLockVoList.add(skuLockVo);
        }
        String errResult = spzxService.checkAndLock(orderForm.getTradeNo(), skuLockVoList, SecurityConstants.INNER).getData();
        //  errResult 如果不是空的，那么锁定出问题了
        Assert.isTrue(!StringUtils.hasText(errResult),errResult);



        // 第二 保存订单和订单详情
        Long orderId = saveOrder(orderForm);
        Assert.notNull(orderId,"保存订单失败");

        // 第三 删除被选中的购物车 调用 cart 服务
        remoteCartService.deleteCartCheckedList(userId, SecurityConstants.INNER);


        // 提交订单未支付时，延迟关单
        // 为什么要用消息队列写：占spring本身线程，有很多延迟关单时，占更多
        rabbitService.sendDelayMessage(MqConst.EXCHANGE_CANCEL_ORDER, MqConst.ROUTING_CANCEL_ORDER, String.valueOf(orderId));



        // 作业，给客户发短信，说已经取消订单
        rabbitService.sendMessage(MqConst.EXCHANGE_TEST,MqConst.ROUTING_TEST,String.valueOf(orderId));



        return orderId;
    }

    @Override
    public TradeVo buy(Long skuId) {
        ProductSku productSku = spzxService.getProductSku(skuId, SecurityConstants.INNER).getData();
        List<OrderItem> orderItemList = new ArrayList<>();
        OrderItem orderItem = new OrderItem();
        orderItem.setSkuId(skuId);
        orderItem.setSkuName(productSku.getSkuName());
        orderItem.setSkuNum(1);
        orderItem.setSkuPrice(productSku.getSalePrice());
        orderItem.setThumbImg(productSku.getThumbImg());
        orderItemList.add(orderItem);

        //订单总金额
        BigDecimal totalAmount = productSku.getSalePrice();

        //渲染订单确认页面-生成用户流水号
        String tradeNo = this.generateTradeNo(SecurityUtils.getUserId());

        TradeVo tradeVo = new TradeVo();
        tradeVo.setTotalAmount(totalAmount);
        tradeVo.setOrderItemList(orderItemList);
        tradeVo.setTradeNo(tradeNo);
        return tradeVo;
    }

    @Override
    public List<OrderInfo> selectUserOrderInfoList(Integer orderStatus) {
//        Long userId = SecurityUtils.getUserId();
//        // 一个userId 可以对很多的 orderInfo
//        List<OrderInfo> orderInfos = orderInfoMapper.selectList(new LambdaQueryWrapper<OrderInfo>().eq(OrderInfo::getUserId, userId));
//        List<OrderInfo> orderInfoList = orderInfos.stream().map(orderInfo -> {
//            orderInfo.setOrderStatus(orderStatus);
//            return orderInfo;
//        }).collect(Collectors.toList());
//        return orderInfoList;
        Long userId = SecurityContextHolder.getUserId();
    // 这行代码值得慢慢看，有太多东西自己不会
        List<OrderInfo> orderInfoList =  orderInfoMapper.selectUserOrderInfoList(userId, orderStatus);
        if(!CollectionUtils.isEmpty(orderInfoList)) {
            List<Long> orderIdList = orderInfoList.stream().map(OrderInfo::getId).collect(Collectors.toList());
            List<OrderItem> orderDetailList = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>().in(OrderItem::getOrderId, orderIdList));
            Map<Long, List<OrderItem>> orderIdToOrderItemListMap = orderDetailList.stream().collect(Collectors.groupingBy(OrderItem::getOrderId));
            orderInfoList.forEach(item -> {
                item.setOrderItemList(orderIdToOrderItemListMap.get(item.getId()));
            });
        }
        return orderInfoList;
    }

    @Override
    public void CloseOrder(long orderId) {
        // 将订单表中的订单状态 改为 -1
        OrderInfo orderInfo = orderInfoMapper.selectById(orderId);
        Assert.notNull(orderInfo,"订单异常");
        if(orderInfo.getOrderStatus()!=0){return;}
        orderInfo.setOrderStatus(-1);
        //课件补充
        orderInfo.setCancelTime(new Date());
        orderInfo.setCancelReason("未支付自动取消");
        orderInfoMapper.updateById(orderInfo);

        // 取消订单后，释放库存
        OrderInfo orderInfoDB = baseMapper.selectById(orderId);
        rabbitService.sendMessage(MqConst.EXCHANGE_PRODUCT, MqConst.ROUTING_UNLOCK, String.valueOf(orderInfoDB.getOrderNo()));

    }

    @Override
    public OrderInfo getOrderInfoByOrderNo(String orderNo) {
        OrderInfo orderInfo = baseMapper.selectOne(new LambdaQueryWrapper<OrderInfo>().eq(OrderInfo::getOrderNo, orderNo));
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(orderInfo.getId());
        List<OrderItem> orderItems = orderItemMapper.selectOrderItemList(orderItem);
        orderInfo.setOrderItemList(orderItems);
        return orderInfo;
    }

    @Override
    public void processPayOrderSucess(String orderNo) {
        OrderInfo orderInfo = orderInfoMapper.selectOne(new LambdaQueryWrapper<OrderInfo>().eq(OrderInfo::getOrderNo, orderNo));
        orderInfo.setOrderStatus(1);
        orderInfoMapper.updateById(orderInfo);
        // 通知 product 开始扣减库存
        rabbitService.sendMessage(MqConst.EXCHANGE_PRODUCT,MqConst.ROUTING_MINUS,orderNo);
    }


    private String getCartKey(Long userId) {
        //定义key user:userId:cart
        return "user:cart:" + userId;
    }

    private String generateTradeNo(Long userId) {
        //1.构建流水号Key
        String userTradeKey = "user:tradeNo:" + userId;
        //2.构建流水号value
        String tradeNo = UUID.randomUUID().toString().replaceAll("-", "");
        //3.将流水号存入Redis 暂存5分钟
        redisTemplate.opsForValue().set(userTradeKey, tradeNo, 5, TimeUnit.MINUTES);
        return tradeNo;
    }

    private Long saveOrder(OrderForm orderForm) {

        Long userId = SecurityContextHolder.getUserId();
        String userName = SecurityContextHolder.getUserName();
        // 保存订单，生成主键

        OrderInfo orderInfo = new OrderInfo();
       // orderInfo.setOrderNo(madeOrderNo()); 因为后面锁定库存 解锁的时候 要用这两个
        orderInfo.setOrderNo(orderForm.getTradeNo());
        orderInfo.setUserId(userId);
        orderInfo.setNickName(userName);
        orderInfo.setRemark(orderForm.getRemark());
        UserAddress userAddress = remoteUserAddressService.getUserAddress(orderForm.getUserAddressId(), SecurityConstants.INNER).getData();
        orderInfo.setReceiverName(userAddress.getName());
        orderInfo.setReceiverPhone(userAddress.getPhone());
        orderInfo.setReceiverTagName(userAddress.getTagName());
        orderInfo.setReceiverProvince(userAddress.getProvinceCode());
        orderInfo.setReceiverCity(userAddress.getCityCode());
        orderInfo.setReceiverDistrict(userAddress.getDistrictCode());
        orderInfo.setReceiverAddress(userAddress.getFullAddress());
        List<OrderItem> orderItemList = orderForm.getOrderItemList();
        BigDecimal totalAmount = new BigDecimal(0);
        for (OrderItem orderItem : orderItemList) {
            totalAmount = totalAmount.add(orderItem.getSkuPrice().multiply(new BigDecimal(orderItem.getSkuNum())));
        }
        orderInfo.setTotalAmount(totalAmount);
        orderInfo.setCouponAmount(new BigDecimal(0));
        orderInfo.setOriginalTotalAmount(totalAmount);
        orderInfo.setFeightFee(orderForm.getFeightFee());
        orderInfo.setOrderStatus(0);
        orderInfoMapper.insert(orderInfo);

        Long id = orderInfo.getId();
        Assert.notNull(id,"订单保存失败");

        // 根据主键，保存订单详情

        for (OrderItem orderItem : orderItemList) {
            orderItem.setOrderId(id);
            orderItemMapper.insert(orderItem);
        }

        // 根据主键，保存订单日志

        OrderLog orderLog = new OrderLog();
        orderLog.setOrderId(orderInfo.getId());
        orderLog.setProcessStatus(0);
        orderLog.setNote("提交订单");
        orderLogMapper.insert(orderLog);

        return id;

    }

    private String madeOrderNo(){
        // 自己制定 创建订单号的规则
        String prefix = "cx";
        String mills = System.currentTimeMillis()+"";
        String ymdhms = new DateTime().toString("YYYYMMddHHmmssSSS");
        return prefix+mills+ymdhms;
    }


}
